'use strict'
import { app, protocol, BrowserWindow, ipcMain, shell } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
const Shortcut = require("electron-localshortcut");
const isDevelopment = process.env.NODE_ENV !== 'production'
const path = require('path');

const dgram = require("dgram");
const nettcp = require("net");

const { SerialPort } = require('serialport');
const { InterByteTimeoutParser } = require('@serialport/parser-inter-byte-timeout')

const SerialPortList = [];
const NetPortList = [];
const WinList = [];

// parameters of programs
const Store = require("electron-store");
const store = new Store();

// ui default size
const widthDefault = 800;
const heightDefault = 500;

const softwareConfigsListDefault = [];
const quickCmdParasListDefault = [];
const historyListDefault = [];
const retransmitParasListDefault = [];
const comParasListDefault = [];
const netParasListDefault = [];

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])

async function openUrl(event, url) {
  shell.openExternal(url);
  return 0;
}

async function getSerials() {
  var serials = await SerialPort.list();
  return serials;
}

async function setSerials(event, serial) {
  console.log('setSerials');
  /* check if the serial has opened */
  var in_list_index = -1;
  for (var i = 0; i < SerialPortList.length; i++) {
    if (SerialPortList[i].path == serial.path) {
      in_list_index = i;
      break;
    }
  }

  /* close the serial (not in list means not open berfore)*/
  if (in_list_index != -1) {
    if (SerialPortList[in_list_index].handle.isOpen) {
      SerialPortList[in_list_index].handle.close();
    }
    var message = {
      type: 'com_status',
      path: SerialPortList[in_list_index].path,
      data: 0
    }
    for (var i = 0; i < WinList.length; i++) {
      WinList[i].webContents.send('ipc:recv-messages', message);
    }
    SerialPortList.splice(in_list_index, 1);
    return 0;
  }

  /* open the serial and add it into the list */
  var port = new SerialPort({
    path: serial.path,
    baudRate: parseInt(serial.buadrate),
    autoOpen: false,
  })

  port.open(function (err) {
    if (err) {
      var message = {
        type: 'com_status',
        path: serial.path,
        data: 0,
        info: err.message
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    } else {
      SerialPortList.push(serial);
      SerialPortList[SerialPortList.length - 1].handle = port;
      var message = {
        type: 'com_status',
        path: serial.path,
        data: 1
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    }
  });

  var intervalTmp = parseInt(serial.timeOut);
  if (intervalTmp > 5) {
    const parser = port.pipe(new InterByteTimeoutParser({ interval: intervalTmp }))
    parser.on('data', function (data) {
      var message = {
        type: 'com_message',
        path: serial.path,
        data: data
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });
  } else {
    port.on("data", function (data) {
      var message = {
        type: 'com_message',
        path: serial.path,
        data: data
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });
  }

  return 0;
}

async function setNets(event, net) {
  console.log('setNets');
  console.log(net);

  /* check if the net has opened */
  var in_list_index = -1;
  for (var i = 0; i < NetPortList.length; i++) {
    if (NetPortList[i].name == net.name) {
      in_list_index = i;
      break;
    }
  }

  /* close the net (not in list means not open berfore)*/
  if (in_list_index != -1) {
    if (NetPortList[in_list_index].type == 'UDP') {
      NetPortList[in_list_index].handle.close();
    }
    if (NetPortList[in_list_index].type == 'TCP Server') {
      NetPortList[in_list_index].handle.close();
    }
    if (NetPortList[in_list_index].type == 'TCP Client') {
      NetPortList[in_list_index].handle.end();
    }

    var message = {
      type: 'net_status',
      path: NetPortList[in_list_index].name,
      data: 0
    }
    for (var i = 0; i < WinList.length; i++) {
      WinList[i].webContents.send('ipc:recv-messages', message);
    }
    NetPortList.splice(in_list_index, 1);
    return 0;
  }

  if (net.type == 'UDP') {
    var udp_socket = dgram.createSocket("udp4");

    udp_socket.on("message", (data, remote) => {
      console.log(data)
      var message = {
        type: 'net_message',
        path: net.name,
        addr: remote.address,
        port: remote.port,
        data: data
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });

    udp_socket.on("listening", () => {
      NetPortList.push(net);
      NetPortList[NetPortList.length - 1].handle = udp_socket;
      var message = {
        type: 'net_status',
        path: net.name,
        data: 1
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });

    udp_socket.on("error", (err) => {
      console.log(err.message);
      var message = {
        type: 'net_status',
        path: net.name,
        data: 0,
        info: err.message
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });

    udp_socket.bind(net.localPort);
  }

  if (net.type == 'TCP Server') {
    var tcp_server = nettcp.createServer((client) => {
      NetPortList[i].handle.clientList.push(client);
      client.on('data', (data => {
        console.log(client)
        console.log(data)
        var message = {
          type: 'net_message',
          path: net.name,
          addr: client.remoteAddress,
          port: client.remotePort,
          data: data
        }
        for (var i = 0; i < WinList.length; i++) {
          WinList[i].webContents.send('ipc:recv-messages', message);
        }
      }));
    });
    tcp_server.on('error', (err => {
      console.log(err.message);
      var message = {
        type: 'net_status',
        path: net.name,
        data: 0,
        info: err.message
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    }));
    tcp_server.listen(net.localPort, () => {
      NetPortList.push(net);
      NetPortList[NetPortList.length - 1].handle = tcp_server;
      NetPortList[NetPortList.length - 1].handle.clientList = [];
      var message = {
        type: 'net_status',
        path: net.name,
        data: 1
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });
  }
  if (net.type == 'TCP Client') {
    var tcp_client = nettcp.Socket();
    tcp_client.connect(net.remotePort, net.remoteAddr, () => {
      NetPortList.push(net);
      NetPortList[NetPortList.length - 1].handle = tcp_client;
      var message = {
        type: 'net_status',
        path: net.name,
        data: 1
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });
    tcp_client.on('data', (data) => {
      console.log(data)
      var message = {
        type: 'net_message',
        path: net.name,
        addr: tcp_client.remoteAddress,
        port: tcp_client.remotePort,
        data: data
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    });
    tcp_client.on('error', (err => {
      console.log(err.message);
      var message = {
        type: 'net_status',
        path: net.name,
        data: 0,
        info: err.message
      }
      for (var i = 0; i < WinList.length; i++) {
        WinList[i].webContents.send('ipc:recv-messages', message);
      }
    }));
  }

  return 0;
}

async function sendMessages(event, message) {
  console.log(message);
  if (message.path == 'ALL') {
    for (var i = 0; i < SerialPortList.length; i++) {
      if (SerialPortList[i].handle.isOpen) {
        SerialPortList[i].handle.write(message.data);
      }
    }
    for (var i = 0; i < NetPortList.length; i++) {
      if (NetPortList[i].type == "UDP") {
        NetPortList[i].handle.send(message.data, NetPortList[i].remotePort, NetPortList[i].remoteAddr);
      }
      if (NetPortList[i].type == 'TCP Server') {
        for (var c = 0; c < NetPortList[i].handle.clientList.length; c++) {
          NetPortList[i].handle.clientList[c].write(message.data);
        }
      }
      if (NetPortList[i].type == 'TCP Client') {
        NetPortList[i].handle.write(message.data);
      }
    }
  } else {
    for (var i = 0; i < SerialPortList.length; i++) {
      if (SerialPortList[i].path == message.path) {
        if (SerialPortList[i].handle.isOpen) {
          SerialPortList[i].handle.write(message.data);
        }
        break;
      }
    }
    for (var i = 0; i < NetPortList.length; i++) {
      if (message.path == NetPortList[i].name && NetPortList[i].type == "UDP") {
        NetPortList[i].handle.send(message.data, NetPortList[i].remotePort, NetPortList[i].remoteAddr);
        break;
      }
      if (message.path == NetPortList[i].name && NetPortList[i].type == 'TCP Server') {
        for (var c = 0; c < NetPortList[i].handle.clientList.length; c++) {
          NetPortList[i].handle.clientList[c].write(message.data);
        }
        break;
      }
      if (message.path == NetPortList[i].name && NetPortList[i].type == 'TCP Client') {
        NetPortList[i].handle.write(message.data);
        break;
      }
    }
  }
  return 1;
}

async function getConfigs() {
  console.log('getConfigs');
  var configs = {
    windowWidth: 0,
    windowHeight: 0,
    softwareConfigsList: [],
    quickCmdParasList: [],
    retransmitParasList: [],
    historyList: [],
    comParasList: [],
    netParasList: []
  };

  configs.windowWidth = store.get('window.width', widthDefault);
  configs.windowHeight = store.get('window.height', heightDefault);

  configs.softwareConfigsList = store.get('datas.softwareConfigsList', softwareConfigsListDefault);
  configs.quickCmdParasList = store.get('datas.quickCmdParasList', quickCmdParasListDefault);
  configs.historyList = store.get('datas.historyList', historyListDefault);
  configs.retransmitParasList = store.get('datas.retransmitParasList', retransmitParasListDefault);
  configs.comParasList = store.get('datas.comParasList', comParasListDefault);
  configs.netParasList = store.get('datas.netParasList', netParasListDefault);

  return configs;
}

async function setConfigs(event, config, value) {
  console.log('setConfigs');
  if (config == "softwareConfigsList") {
    store.set('datas.softwareConfigsList', value);
  }
  if (config == "quickCmdParasList") {
    store.set('datas.quickCmdParasList', value);
  }
  if (config == "historyList") {
    store.set('datas.historyList', value);
  }
  if (config == "retransmitParasList") {
    store.set('datas.retransmitParasList', value);
  }
  if (config == "comParasList") {
    store.set('datas.comParasList', value);
  }
  if (config == "netParasList") {
    store.set('datas.netParasList', value);
  }
  return 0;
}

async function createWindow() {
  // Load the stored paras
  let width = store.get('window.width', widthDefault);
  let height = store.get('window.height', heightDefault);
  if (width >= 1024) {
    width = widthDefault;
  }
  if (height >= 768) {
    height = heightDefault;
  }

  // Create the browser window.
  const win = new BrowserWindow({
    minWidth: widthDefault,
    minHeight: heightDefault,

    width: width,
    height: height,

    frame: false,
    icon: path.join(__dirname, "./public/logo.png"),

    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      webSecurity: false,
    }
  })

  Shortcut.register(win, "F12", () => {
    console.log("Pressed F12");
    win.webContents.openDevTools();
  })

  // win 
  ipcMain.on('ipc:set-window-min', function () {
    win.minimize();
  })
  ipcMain.on('ipc:set-window-max', function () {
    if (win.isMaximized()) {
      win.restore();
    } else {
      win.maximize();
    }
  })
  ipcMain.on('ipc:set-window-exit', function () {
    let [width, height] = win.getSize();
    store.set('window.width', width);
    store.set('window.height', height);
    win.close();
  })

  // url 
  ipcMain.handle('ipc:open-url', openUrl);

  // config
  ipcMain.handle('ipc:get-configs', getConfigs);
  ipcMain.handle('ipc:set-configs', setConfigs);

  // serial
  ipcMain.handle('ipc:get-serials', getSerials);
  ipcMain.handle('ipc:set-serials', setSerials);

  // net
  ipcMain.handle('ipc:set-nets', setNets);

  // message
  ipcMain.handle('ipc:send-messages', sendMessages);


  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) {
      //win.webContents.openDevTools()
    }
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }

  WinList.push(win);
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  for (var i = 0; i < SerialPortList.length; i++) {
    if (SerialPortList[i].handle.isOpen) {
      SerialPortList[i].handle.close();
    }
  }
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
  console.log("window-all-closed");
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}
